from collections.abc import Iterable

from django.core.files import File
from rest_framework import serializers

from sysreptor import signals as sysreptor_signals
from sysreptor.pentests.import_export.serializers.common import (
    ExportImportSerializer,
    FileExportImportSerializer,
    MultiFormatSerializer,
)
from sysreptor.pentests.models import (
    ProjectType,
    ReviewStatus,
    SourceEnum,
    UploadedAsset,
)
from sysreptor.utils.fielddefinition.types import (
    FieldDefinition,
    parse_field_definition_legacy,
    serialize_field_definition,
)


class UploadedAssetExportImportSerializer(FileExportImportSerializer):
    class Meta(FileExportImportSerializer.Meta):
        model = UploadedAsset

    def get_linked_object(self):
        return self.context['project_type']

    def get_path_in_archive(self, name):
        # Get ID of old project_type from archive
        return str(self.context.get('project_type_id') or self.get_linked_object().id) + '-assets/' + name


class ProjectTypeImportSerializerV1(ExportImportSerializer):
    assets = UploadedAssetExportImportSerializer(many=True)
    finding_fields = serializers.DictField()
    finding_field_order = serializers.ListField(child=serializers.CharField())
    report_fields = serializers.DictField()
    report_sections = serializers.ListField(child=serializers.DictField())

    class Meta:
        model = ProjectType
        fields = [
            'id', 'created', 'updated',
            'name', 'language', 'status', 'tags',
            'report_fields', 'report_sections',
            'finding_fields', 'finding_field_order', 'finding_ordering',
            'default_notes',
            'report_template', 'report_styles', 'report_preview_data',
            'assets',
        ]
        extra_kwargs = {
            'id': {'read_only': False},
            'created': {'read_only': False, 'required': False},
            'status': {'required': False, 'default': ReviewStatus.FINISHED},
        }

    def create(self, validated_data):
        old_id = validated_data.pop('id')
        assets = validated_data.pop('assets', [])

        # Load old field definition format
        try:
            finding_fields = serialize_field_definition(parse_field_definition_legacy(
                field_dict=validated_data.pop('finding_fields', {}),
                field_order=validated_data.pop('finding_field_order', []),
            ))
            report_fields = parse_field_definition_legacy(field_dict=validated_data.pop('report_fields', {}))
            report_sections = validated_data.pop('report_sections', [])
            for section_data in report_sections:
                section_data['fields'] = serialize_field_definition(FieldDefinition(fields=[report_fields[f_id] for f_id in section_data['fields']]))
        except Exception as ex:
            raise serializers.ValidationError('Invalid field definition') from ex

        project_type = super().create({
            'source': SourceEnum.IMPORTED,
            'finding_fields': finding_fields,
            'report_sections': report_sections,
            'skip_post_create_signal': True,
        } | validated_data)
        project_type.full_clean()

        self.context.update({'project_type': project_type, 'project_type_id': old_id})
        self.fields['assets'].create(assets)

        sysreptor_signals.post_create.send(sender=project_type.__class__, instance=project_type)
        return project_type


class ProjectTypeExportImportSerializerV2(ExportImportSerializer):
    assets = UploadedAssetExportImportSerializer(many=True)

    class Meta:
        model = ProjectType
        fields = [
            'id', 'created', 'updated',
            'name', 'language', 'status', 'tags',
            'report_sections', 'finding_fields', 'finding_ordering', 'finding_grouping',
            'default_notes',
            'report_template', 'report_styles', 'report_preview_data',
            'assets',
        ]
        extra_kwargs = {
            'id': {'read_only': False},
            'created': {'read_only': False, 'required': False},
            'status': {'required': False, 'default': ReviewStatus.FINISHED},
        }

    def to_representation(self, instance):
        self.context.update({'project_type': instance})
        return super().to_representation(instance)

    def export_files(self, instance) -> Iterable[tuple[str, File]]:
        af = self.fields['assets']
        self.context.update({'project_type': instance})
        yield from af.export_files(instance=list(af.get_attribute(instance).all()))

    def create(self, validated_data):
        old_id = validated_data.pop('id')
        assets = validated_data.pop('assets', [])
        project_type = super().create({
            'source': SourceEnum.IMPORTED,
            'skip_post_create_signal': True,
        } | validated_data)
        self.context.update({'project_type': project_type, 'project_type_id': old_id})
        self.fields['assets'].create(assets)
        sysreptor_signals.post_create.send(sender=project_type.__class__, instance=project_type)
        return project_type


class ProjectTypeExportImportSerializer(MultiFormatSerializer):
    serializer_formats = {
        'projecttypes/v2': ProjectTypeExportImportSerializerV2(),
        'projecttypes/v1': ProjectTypeImportSerializerV1(),
    }

